﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Drawing;
using System.Windows.Forms;
using ArithmeticLibrary;

namespace GraphicLibrary
{
    /// <summary>
    /// Trida reprezentujici objekt starajici se o kresleni.
    /// </summary>
    public class FunctionDrawer
    {
        #region Fields

        /// <summary>
        /// Funkce.
        /// </summary>
        Function function;

        /// <summary>
        /// Tabulka funkcnich hodnot funkce.
        /// </summary>
        FunctionPoint[] functionPoints;

        /// <summary>
        /// Tabulka bodu k vykresleni.
        /// </summary>
        Point[] points;

        /// <summary>
        /// Maximum x.
        /// </summary>
        double maxX;

        /// <summary>
        /// Maximum y.
        /// </summary>
        double maxY;

        /// <summary>
        /// Minimum x.
        /// </summary>
        double minX;

        /// <summary>
        /// Minimum y.
        /// </summary>
        double minY;

        /// <summary>
        /// Presnost vykreslovani.
        /// </summary>
        double precision;

        /// <summary>
        /// Zacatek intervalu pro vykresleni.
        /// </summary>
        double start;

        /// <summary>
        /// Konec intervalu pro vykresleni.
        /// </summary>
        double end;

        #endregion // Fields

        #region Properties

        /// <summary>
        /// Objekt starajici se o samotne vykresleni.
        /// </summary>
        Graphics Graphics { get; set; }

        /// <summary>
        /// Panel, do ktereho se bude kreslit.
        /// </summary>
        Panel Panel { get; set; }

        #endregion // Properties

        #region Constructors

        /// <summary>
        /// Vytvori instanci objektu pro vykresleni funkce do panelu.
        /// </summary>
        /// <param name="graphics">Objekt provadejici vykreslovani.</param>
        /// <param name="Panel">Panel, do ktereho se bude kreslit.</param>
        /// <param name="function">Funkce na vykresleni.</param>
        /// <param name="start">Zacatek intervalu, ktery se ma vykreslit.</param>
        /// <param name="end">Konec intervalu, ktery se ma vykreslit.</param>
        public FunctionDrawer(Graphics graphics, Panel Panel, Function function, double start, double end)
        {
            this.Graphics = graphics;
            this.Panel = Panel;
            this.function = function;
            this.start = start;
            this.end = end;
        }

        #endregion // Constructors

        #region Public methods

        /// <summary>
        /// Nakresli funkci do panelu.
        /// </summary>
        public void DrawFunction()
        {
            try
            {
                // vypocita funkcni hodnoty
                ProceedFunction();

                // vypocita z funkcnich hodnot body na vykresleni
                GetPoints();

                // nakresli osy x a y
                DrawAxes(20, 16);

                // nakresli bocni osy x a y
                DrawSideAxes(20, 16);

                // vykresli krivku
                Graphics.DrawCurve(Pens.Red, points);
            }
            catch (Exception e)
            {
                MessageBox.Show("Nelze podle zadaných parametrů nakreslit funkci.\n" + e.Message);
                return;
            }
        }

        #endregion // Public methods

        #region Private methods

        /// <summary>
        /// Zpracuje funkci. Vypocita funkcni hodnoty a ulozi je do tabulky hodnot.
        /// </summary>
        private void ProceedFunction()
        {
            maxX = end;
            minX = start;

            // automaticky nastavime presnost podle delky intervalu
            double range = maxX - minX;
            precision = range / (Panel.Width - 160);
            
            List<FunctionPoint> list = new List<FunctionPoint>();
            List<Point> list2 = new List<Point>();

            double x = minX;
            minY = function.Evaluate(x);
            maxY = function.Evaluate(x);

            double y = 0;

            while (x < maxX)
            {
                try
                {
                    y = function.Evaluate(x);

                    if (double.IsNaN(y) || double.IsInfinity(y))
                        throw new Exception("Nedefinovaná funkční hodnota v bodě " + function.VariableName + " = " + x);

                    Convert.ToInt32(y);
                }
                catch (OverflowException)
                {
                    x += precision;
                    continue;
                }

                list.Add(new FunctionPoint(x, y));

                if (y > maxY) maxY = y;

                if (y < minY) minY = y;

                x += precision;
            }

            functionPoints = list.ToArray();
        }

        /// <summary>
        /// Vezme tabulku funkcnich hodnot a vypocita pro kazdou hodnotu bod na vykresleni.
        /// </summary>
        private void GetPoints()
        {
            points = new Point[functionPoints.Length];

            for (int i = 0; i < functionPoints.Length; i++)
            {
                points[i] = new Point(GetXCoordinate(functionPoints[i].X),
                    GetYCoordinate(functionPoints[i].Y));
            }
        }

        /// <summary>
        /// Priradi x-ove hodnote x-ovou souradnici pro vykresleni.
        /// </summary>
        /// <param name="x">Funkcni hodnota x.</param>
        /// <returns>Souradnici hodnoty x pro vykresleni.</returns>
        private int GetXCoordinate(double x)
        {
            // d ~ tolik pixelu na 1 bod x
            double d = (Panel.Width - 160) / (Math.Abs(maxX - minX));

            return Convert.ToInt32(((x - minX) * d) + 120);
        }

        /// <summary>
        /// Priradi y-nove hodnote y-novou souradnici pro vykresleni.
        /// </summary>
        /// <param name="y">Funkcni hodnota y.</param>
        /// <returns>Souradnici hodnoty y pro vykresleni.</returns>
        private int GetYCoordinate(double y)
        {
            // d = tolik pixelu na 1 bod y
            double d = (Panel.Height - 80) / (Math.Abs(maxY - minY));

            return Convert.ToInt32(Panel.Height - 40 - ((y - minY) * d));
        }

        /// <summary>
        /// Nakresli osy x a y.
        /// </summary>
        /// <param name="xSteps">Pocet dilku na ose x.</param>
        /// <param name="ySteps">Pocet dilku na ose y.</param>
        private void DrawAxes(int xSteps, int ySteps)
        {
            // pokud se nachazi nula na ose x
            if (maxX > 0 && minX < 0)
            {
                // nakreslime osu y
                Graphics.DrawLine(Pens.Black, GetXCoordinate(0), GetYCoordinate(maxY),
                    GetXCoordinate(0), GetYCoordinate(minY));
            }

            // pokud se nachazi nula na ose y
            if (maxY > 0 && minY < 0)
            {
                // nakreslime osu x
                Graphics.DrawLine(Pens.Black, GetXCoordinate(maxX), GetYCoordinate(0),
                    GetXCoordinate(minX), GetYCoordinate(0));
            }
        }

        /// <summary>
        /// Nakresli bocni osy x a y.
        /// </summary>
        /// <param name="xSteps">Pocet dilku na ose x.</param>
        /// <param name="ySteps">Pocet dilku na ose y.</param>
        private void DrawSideAxes(int xSteps, int ySteps)
        {
            // dolni osa x
            Graphics.DrawLine(Pens.Gray, 100, Panel.Height - 20, Panel.Width - 20, Panel.Height - 20);

            // leva osa y
            Graphics.DrawLine(Pens.Gray, 100, 20, 100, Panel.Height - 20);

            System.Drawing.Font font = new Font("Consolas", 10);
            Brush brush = new SolidBrush(Color.Gray);
            RectangleF rectangle;
            // funkcni hodnoty x
            int[] x = new int[xSteps];
            double step = Math.Abs(maxX - minX) / xSteps;
            for (int i = 0; i < x.Length; i++)
            {
                x[i] = Convert.ToInt32(step * i + minX);
            }
            // dilky na ose x
            for (int i = 0; i < x.Length; i++)
            {
                Graphics.DrawLine(Pens.Gray, GetXCoordinate(x[i]), Panel.Height - 25,
                    GetXCoordinate(x[i]), Panel.Height - 20);

                rectangle = new RectangleF(GetXCoordinate(x[i]) - 5, Panel.Height - 20, 50, 15);
               
                Graphics.DrawString(x[i].ToString(), font, brush, rectangle);
            }

            int[] y = new int[ySteps];
            step = Math.Abs(maxY - minY) / ySteps;

            for (int i = 0; i < y.Length; i++)
            {
                y[i] = Convert.ToInt32(step * i + minY);
            }
            // dilky na ose y
            for (int i = 0; i < y.Length; i++)
            {
                Graphics.DrawLine(Pens.Gray, 100, GetYCoordinate(y[i]), 105, GetYCoordinate(y[i]));

                rectangle = new RectangleF(30, GetYCoordinate(y[i]) - 7, 50, 15);
                
                Graphics.DrawString(y[i].ToString(), font, brush, rectangle);
            }

        }

        #endregion // Private methods

    }
}
